/*
 * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.iec.edp.caf.rpc.remote.grpc.service;

import io.iec.edp.caf.common.JSONSerializer;
import io.iec.edp.caf.commons.core.SerializerFactory;
import io.iec.edp.caf.commons.core.enums.SerializeType;
import io.iec.edp.caf.commons.utils.SpringBeanUtils;
import io.iec.edp.caf.core.context.ICAFContextService;
import io.iec.edp.caf.logging.CommonConstant;
import io.iec.edp.caf.rpc.api.entity.RpcServiceMethodDefinition;
import io.iec.edp.caf.rpc.api.event.RpcClientEventBroker;
import io.iec.edp.caf.rpc.api.serialize.RpcSerializeUtil;
import io.iec.edp.caf.rpc.api.support.ConstanceVarible;
import io.iec.edp.caf.rpc.api.support.RpcTimeoutHolder;
import io.iec.edp.caf.rpc.api.support.Type;
import io.iec.edp.caf.rpc.remote.grpc.CafGrpcChannel;

import io.iec.edp.caf.rpc.server.grpc.RpcRemoteInvokerPlus;
import lombok.SneakyThrows;

import javax.servlet.http.Cookie;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.*;

import static java.util.stream.Collectors.toList;

public class GrpcRemoteInvokerImpl implements RpcRemoteInvokerPlus {

    private ICAFContextService contextService = SpringBeanUtils.getBean(ICAFContextService.class);

    private RpcClientEventBroker clientEventBroker;
    public GrpcRemoteInvokerImpl(RpcClientEventBroker clientEventBroker) {
        this.clientEventBroker = clientEventBroker;
        //this.rpcAddressDiscover = rpcAddressDiscover;
    }

    /**
     * invoke remote rpc service
     * @param type                       return value type
     * @param serviceId                  serviceid
     * @param parameters                 service parameters
     * @param cks                        cookies
     * @param rpcServiceMethodDefinition service definition
     * @param <T>                        return type
     * @return
     * @throws IOException
     * @throws ClassNotFoundException
     * @throws NoSuchMethodException
     * @throws InstantiationException
     * @throws IllegalAccessException
     * @throws InvocationTargetException
     */
    @SneakyThrows
    @Override
    public <T> T invokeRemoteService(Type<T> type, String serviceId, String serviceUnitName,String remoteUrl, HashMap<String, Object> parameters, Cookie[] cks, RpcServiceMethodDefinition rpcServiceMethodDefinition, Integer tenantId,HashMap<String, String> eventContext){

        CafGrpcChannel grpcChannel = new CafGrpcChannel(remoteUrl);

        //gsp context
        HashMap<String, String> gspContext = new HashMap<>();
        setGSPContext(gspContext,cks,serviceUnitName,tenantId,eventContext);

        //event context
        setEventContext(eventContext,rpcServiceMethodDefinition);

        //todo 这个判断不合适 不应依赖能否找到definition 后期调整
        LinkedHashMap<String, String> params = RpcSerializeUtil.serializeParameter(parameters, rpcServiceMethodDefinition);

        HashMap<String, Object> localDict = new HashMap<>();
        localDict.put(CommonConstant.LOG_MSU_ID, serviceUnitName);
        //before invoke event
        this.clientEventBroker.firePreRpcInvokeEvent(eventContext, parameters, localDict);

        try {
            //remote invoke
            Object result = grpcChannel.invoke(serviceId, params,JSONSerializer.serialize(gspContext));
            grpcChannel.shutDown();
            //this.clientEventBroker.firePostRpcInvokeEvent(serverContextDict, result, localDict);

            T finalResult = RpcSerializeUtil.deSerializeReturnValue(type, result, rpcServiceMethodDefinition);

            return finalResult;
        } catch (Exception ex) {
            this.clientEventBroker.fireExceptionRpcInvokeEvent(eventContext, parameters, localDict, ex);
//            var e = handleException(ex,cks,serviceId,serviceUnitName);
//            throw e;
            throw ex;
        }finally {
            RpcTimeoutHolder.clearTimeout();
        }
    }

    private void setGSPContext(Map<String,String> gspContext, Cookie[] cks, String serviceUnitName, Integer tenantId, Map<String,String> eventContext){
        String sessionId = "";
        //set some context for early net core
        if (cks != null && cks.length > 0) {
            List<Cookie> ck = Arrays.stream(cks).filter(c -> c.getName().equalsIgnoreCase(ConstanceVarible.NET_SESSON_ID)).collect(toList());
            sessionId = (ck != null && ck.size() > 0) ? ck.get(0).getValue() : sessionId;
        }

        gspContext.put(ConstanceVarible.GSP_CONTEXT_ID, sessionId);
        gspContext.put(ConstanceVarible.GSP_RPC, "true");
        gspContext.put(ConstanceVarible.GSP_MSU, serviceUnitName);
        if (tenantId != null) {
            gspContext.put(ConstanceVarible.GSP_RPC_TENANT, tenantId.toString());
        }
        gspContext.put(ConstanceVarible.GSP_RPC_CLIENT_ENVENT, SerializerFactory.getSerializer(SerializeType.Json).serializeToString(eventContext));

    }


    private void setEventContext(Map<String,String> eventContext,RpcServiceMethodDefinition rpcServiceMethodDefinition){
        eventContext.put(ConstanceVarible.CURRENT_SERVICE_INTERFACE, rpcServiceMethodDefinition == null ? null : rpcServiceMethodDefinition.getParentDefinition().getClassName());
    }
}
